use super::{err, err_value, json, throw_err, Base, ECode, MapSv, VBase, VMap, Value};
use chrono::prelude::*;
use std::str::FromStr;

pub trait CBase: Base {
    fn normal_str(&self, d: &str) -> String {
        d.trim_matches('\"')
            .replace(" ", r#"\ "#)
            .replace("=", r#"\="#)
            .replace(",", r#"\,"#)
    }

    fn is_ignore(&self, d: &Value) -> bool {
        if let Some(v) = d.as_str() {
            let tmp = v.trim();
            if tmp.is_empty() {
                return true;
            }

            if let Ok(tmp) = i64::from_str(tmp) {
                if tmp == 0 {
                    return true;
                }
            }

            if let Ok(tmp) = f64::from_str(tmp) {
                if tmp.abs() <= 0.0000000001 {
                    return true;
                }
            }
        } else if let Some(v) = d.as_i64() {
            if v == 0 {
                return true;
            }
        } else if let Some(v) = d.as_f64() {
            if v.abs() <= 0.0000000001 {
                return true;
            }
        }

        return false;
    }

    fn field_val(&self, k: &str, d: &Value, rmempty: bool) -> Result<Option<String>, Value> {
        if d.is_null() {
            throw_err!(ECode::JsonFormat, format!("val not be empty: {}", k));
        }

        if rmempty {
            if self.is_ignore(d) {
                return Ok(None);
            }
        }

        if let Some(v) = d.as_str() {
            Ok(Some(format!(r#"{}="{}""#, k, v)))
        } else if let Some(v) = d.as_i64() {
            Ok(Some(format!("{}={}", k, v)))
        } else if let Some(v) = d.as_f64() {
            Ok(Some(format!("{}={}", k, v)))
        } else if let Some(v) = d.as_bool() {
            Ok(Some(format!("{}={}", k, v)))
        } else {
            err!(ECode::JsonFormat, format!("field format error: {k}"))
        }
    }

    fn compare(&self, data: &MapSv) -> Option<String> {
        let kv = |key| {
            let v = data.k_str_trim(key)?;
            let op = match key {
                "eq" => "=",
                "le" => "<=",
                "ge" => ">=",
                "lt" => "<",
                "gt" => ">",
                _ => "",
            };

            if op.is_empty() {
                return None;
            }

            if v == "tm" {
                let val = data.k_str("v")?.to_string();
                return Some(format!(r#"time{}{}"#, op, val));
            }

            if v == "time" {
                let mut val = data.k_str("v")?.to_string();

                if let Some(utc) = data.k_f64("utc") {
                    val = self.utc_offset(&val, utc, None)?;
                } else if let Some(utc) = data.k_i64("utc") {
                    val = self.utc_offset(&val, utc as f64, None)?;
                }

                return Some(format!(r#"{}{}'{}'"#, v, op, val));
            }

            if let Some(val) = data.k_str("v") {
                Some(format!(r#"{}{}'{}'"#, v, op, val))
            } else if let Some(val) = data.k_i64("v") {
                Some(format!(r#"{}{}{}"#, v, op, val))
            } else if let Some(val) = data.k_f64("v") {
                Some(format!(r#"{}{}{}"#, v, op, val))
            } else if let Some(val) = data.k_bool("v") {
                Some(format!(r#"{}{}{}"#, v, op, val))
            } else {
                None
            }
        };

        if let Some(v) = kv("eq") {
            Some(v)
        } else if let Some(v) = kv("le") {
            Some(v)
        } else if let Some(v) = kv("ge") {
            Some(v)
        } else if let Some(v) = kv("lt") {
            Some(v)
        } else if let Some(v) = kv("gt") {
            Some(v)
        } else {
            None
        }
    }

    fn filter(&self, key: &str, data: &MapSv) -> Result<Option<String>, Value> {
        if !data.contains_key(key) {
            return Ok(None);
        }

        let data = data.ck_array(key)?;

        if data.len() > 20 {
            throw_err!(ECode::Para, "opt num must <= 20");
        }

        let mut ret = Vec::new();

        for item in data.iter() {
            let v = item.ckobj()?;
            let v = self.compare(v).ok_or(json!("filter compare error"))?;
            ret.push(v);
        }

        let jn = format!(" {} ", key);
        let ret = ret.join(&jn);

        if ret.is_empty() {
            return Ok(None);
        }

        Ok(Some(ret))
    }

    fn opt_filter(&self, para: &MapSv) -> Result<Option<(String, usize)>, Value> {
        let data = match para.k_obj("opt") {
            Some(v) => v,
            None => return Ok(None),
        };

        let mut ret = "".to_string();

        if let Some(v) = self.filter("and", data)? {
            ret += &format!("where ({} )", v);
        } else if let Some(v) = self.filter("or", data)? {
            ret += &format!("where ({} )", v);
        }

        if let Some(ft) = para.k_obj("filter") {
            if let Some(v) = self.filter("and", ft)? {
                ret += &format!("and ({} )", v);
            } else if let Some(v) = self.filter("or", ft)? {
                ret += &format!("and ({} )", v);
            }
        }

        if let Some(v) = data.k_bool("desc") {
            if v {
                ret += "order by time desc ";
            }
        }

        let limit = match data.k_i64("count") {
            Some(v) => {
                ret += &format!("limit {} ", v);
                0
            }
            None => match data.k_i64("limit") {
                Some(v) => {
                    if v > 0 {
                        let tmp = if v > 1000 { 2 * v } else { 2000 };

                        ret += &format!("limit {} ", tmp);
                    }

                    v as usize
                }
                None => 0,
            },
        };

        if ret.is_empty() {
            return Ok(None);
        }

        Ok(Some((ret, limit)))
    }

    fn parse_str(&self, data: &str, ti: bool) -> String {
        if data == "*" {
            return "*".to_string();
        }

        let mut ret: Vec<&str> = data.split(",").map(|t| t.trim()).collect();
        if ti {
            if let None = ret.iter().find(|&&x| x == "ti") {
                ret.push("ti");
            }

            if let None = ret.iter().find(|&&x| x == "dtp") {
                ret.push("dtp");
            }
        }

        let ret: Vec<String> = ret.iter().map(|x| format!(r#""{}""#, x)).collect();
        ret.join(",")
    }

    fn sql_parse(&self, data: &MapSv) -> Result<(Vec<String>, usize), Value> {
        let tab = data.ck_str_trim("n")?;
        let tab = self.parse_str(tab, false);

        let obj = data.ck_str_trim("obj")?;
        let obj = self.parse_str(obj, true);

        let mut opt = "".to_string();
        let limit = match self.opt_filter(data)? {
            Some(v) => {
                opt += &v.0;
                v.1
            }
            None => 0,
        };

        let mut ret = Vec::new();

        let tab: Vec<&str> = tab.split(",").map(|x| x.trim()).collect();
        for t in tab.iter() {
            ret.push(format!(r#"select {} from {} {}"#, obj, t, opt));
        }

        Ok((ret, limit))
    }

    /* {
     *    "tab": "xxx",
     *    "time": "xxx",
     *    "tag": {
     *      "k": "v"
     *    }
     *    "field": {
     *      "k": "v"
     *    }
     * }
     */
    fn tag_field(
        &self,
        data: &Value,
        rmempty: bool,
        tindex: usize,
        ti: bool,
    ) -> Result<String, Value> {
        let para = data.ckobj()?;

        let tab = para.ck_str_trim("tab")?;
        let tag = para.ck_obj("tag")?;

        let mut wtag = Vec::new();
        for (k, v) in tag.iter() {
            let k = k.trim();
            if k.is_empty() {
                continue;
            }

            if ti && k == "ti" {
                throw_err!(ECode::JsonFormat, "ti duplicate");
            }

            let v = v.ckstr()?;
            if v.is_empty() {
                continue;
            }

            let v = self.normal_str(v);
            wtag.push(format!("{}={}", k, v));
        }

        if wtag.is_empty() {
            throw_err!(ECode::JsonFormat, "tag donot be empty");
        }

        if ti {
            wtag.push(format!("ti={}", tindex));
        }

        let mut wfield = Vec::new();
        let mut dtp = false;

        let field = para.ck_obj("field")?;
        for (k, v) in field.iter() {
            let k = k.trim();
            if k.is_empty() {
                continue;
            }

            if k == "ti" || k == "usn" || tag.contains_key(k) {
                throw_err!(
                    ECode::JsonFormat,
                    format!("field and tag not duplicates: {k}")
                );
            }

            if k == "msn" {
                let m = v.ckstr()?;
                if m.is_empty() {
                    continue;
                }

                let m = self.normal_str(m);
                wtag.push(format!("msn={}", m));
                continue;
            }

            if k == "dtp" {
                dtp = true;
            }

            let v = self.field_val(k, v, rmempty)?;

            if let Some(val) = v {
                wfield.push(val);
            }
        } // for

        if wfield.is_empty() || !dtp {
            wfield.push("dtp=0".to_string());
        }

        let wtag = wtag.join(",");
        let wfield = wfield.join(",");

        let mut q = format!("{},{} {}", tab, wtag, wfield);
        if let Some(time) = para.k_str_trim("time") {
            q = format!("{} {}", q, time);
        }

        Ok(q)
    }

    fn utc_offset(&self, val: &str, utc: f64, fmt: Option<&str>) -> Option<String> {
        let utc = utc * 3600.0;
        let tm = DateTime::parse_from_rfc3339(val).ok()?;
        let tm = tm + FixedOffset::west_opt(utc as i32)?;

        let tm = match fmt {
            Some(v) => tm.format(v).to_string(),
            None => tm.to_rfc3339_opts(SecondsFormat::Millis, true).to_string(),
        };

        Some(tm)
    }
}
